
package org.movee.spring.data.hugegraph.repository.factory;

import org.movee.spring.data.hugegraph.mapping.HugeMappingContext;
import org.movee.spring.data.hugegraph.mapping.HugePersistentEntity;
import org.movee.spring.data.hugegraph.query.HugeOperations;
import org.movee.spring.data.hugegraph.repository.SimpleHugeRepository;
import org.movee.spring.data.hugegraph.repository.query.HugeEntityInformation;
import org.movee.spring.data.hugegraph.repository.query.HugeQueryMethod;
import org.movee.spring.data.hugegraph.repository.query.MappingHugeEntityInformation;
import org.movee.spring.data.hugegraph.repository.query.PartTreeHugeRepositoryQuery;
import org.movee.spring.data.hugegraph.repository.query.StringBasedHugeRepositoryQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Optional;

public class HugeRepositoryFactory extends RepositoryFactorySupport {

    private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    private final HugeMappingContext mappingContext;
    private final HugeOperations operations;

    public HugeRepositoryFactory(HugeOperations operations) {

        Assert.state(operations != null, "GremlinOperations must not be null");

        this.operations = operations;
        this.mappingContext = operations.getConverter().getMappingContext();
    }

    /**
     * repository bean实现的基类
     * @param metadata
     * @return
     */
    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
        return SimpleHugeRepository.class;
    }

    /**
     * 为用 @Repository 注解的接口创建一个实例, 即 SimpleGremlinRepository 的实例
     * @param information
     * @return
     */
    @Override
    protected Object getTargetRepository(RepositoryInformation information) {
        HugeEntityInformation<?, ?> entityInformation = getEntityInformation(information.getDomainType());

        // 创建SimpleGremlinRepository实例，后面两个参数为构造器参数
        return getTargetRepositoryViaReflection(information, entityInformation, operations);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T, ID> HugeEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
        HugePersistentEntity<?> entity = mappingContext.getPersistentEntity(domainClass);
        return new MappingHugeEntityInformation<>((HugePersistentEntity<T>) entity, operations.getConverter());
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(Key, EvaluationContextProvider)
     */
    @Override
    protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
                                                  QueryMethodEvaluationContextProvider evaluationContextProvider) {
        return Optional.of(new GremlinQueryLookupStrategy(operations, evaluationContextProvider));
    }

    private static class GremlinQueryLookupStrategy implements QueryLookupStrategy {

        private final QueryMethodEvaluationContextProvider evaluationContextProvider;
        private final HugeMappingContext mappingContext;
        private final HugeOperations operations;

        public GremlinQueryLookupStrategy(HugeOperations operations,
                                          QueryMethodEvaluationContextProvider evaluationContextProvider) {
            this.operations = operations;
            this.evaluationContextProvider = evaluationContextProvider;
            this.mappingContext = operations.getConverter().getMappingContext();
        }

        @Override
        public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
                                            ProjectionFactory factory, NamedQueries namedQueries) {

            HugeQueryMethod queryMethod = new HugeQueryMethod(method, metadata, factory, mappingContext);
            String namedQueryName = queryMethod.getNamedQueryName();
            if (namedQueries.hasQuery(namedQueryName)) {
                String namedQuery = namedQueries.getQuery(namedQueryName);
                return new StringBasedHugeRepositoryQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
                        evaluationContextProvider);
            } else if (queryMethod.hasAnnotatedQuery()) {
                return new StringBasedHugeRepositoryQuery(queryMethod, operations, EXPRESSION_PARSER,
                        evaluationContextProvider);
            } else {
                return new PartTreeHugeRepositoryQuery(queryMethod, operations);
            }
        }
    }
}
